package alingrad.engine.src.collisions;

import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;

/**
 * A collision mask wrapper class that contains information on the mask.
 * @author alingrad
 *
 */
public class CollisionMask {
	/**
	 * The x position of the origin.
	 */
	private int x;

	/**
	 * The y position of the origin.
	 */
	private int y;

	/**
	 * The rotation of the mask.
	 */
	private float rotation;

	/**
	 * The scale of the mask.
	 */
	private float scale;

	/**
	 * The width of the mask.
	 */
	private int width;

	/**
	 * The height of the mask.
	 */
	private int height;

	/**
	 * The right end of the mask.
	 */
	private int right;

	/**
	 * The bottom end of the mask.
	 */
	private int bottom;

	/**
	 * The left end of the mask.
	 */
	private int left;

	/**
	 * The top end of the mask.
	 */
	private int top;

	/**
	 * Whether the mask should be mirrored across the x-axis.
	 */
	private boolean xmirror;

	/**
	 * Whether the mask should be mirrored across the y-axis.
	 */
	private boolean ymirror;

	/**
	 * The data.
	 */
	private CollisionMaskData data;

	/**
	 * Transformation matrix.
	 */
	private Matrix4f matrix;

	/**
	 * Constructs a new collision mask.
	 * @param width the width of the mask.
	 * @param height the height of the mask.
	 */
	public CollisionMask(CollisionMaskData data) {
		this.data = data;
		width = data.getWidth();
		height = data.getHeight();
		scale = 1;
		rotation = 0;
		calculateMatrix();
	}

	/**
	 * sets the x and y position of the mask.
	 * @param x the x position.
	 * @param y the y position.
	 */
	public void set(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void set(int x, int y, float rotation) {
		this.rotation = rotation;
		set(x, y);
	}

	public void set(int x, int y, float rotation, float scale) {
		this.scale = scale;
		this.rotation = rotation;
		set(x, y);
	}

	public void set(int x, int y, float rotation, float scale, boolean xmirror, boolean ymirror) {
		this.scale = scale;
		this.rotation = rotation;
		this.xmirror = xmirror;
		this.ymirror = ymirror;
		set(x, y);
	}

	private void calculateMatrix() {
		matrix = new Matrix4f();
		matrix.translate(new Vector3f(x, y, 0));
		int xm = 1, ym = 1;
		if (xmirror)
			xm = -1;
		if (ymirror)
			ym = -1;
		matrix.scale(new Vector3f(xm, ym, 1));
		matrix.rotate((float)Math.toRadians(rotation), new Vector3f(0, 0, 1));
		matrix.scale(new Vector3f(scale, scale, 1));
		matrix.translate(new Vector3f(-width / 2, -height / 2, 0));
	}

	private void calculateBoundingBox() {
		if ((rotation == 0 || rotation == 180) && scale == 1f && !xmirror && !ymirror) {
			top = y - height / 2;
			left = x - width / 2;
			right = left + width;
			bottom = top + height;
		}else {
			calculateMatrix();
            Vector2f leftTop = new Vector2f(0, 0);
            Vector2f rightTop = new Vector2f(width, 0);
            Vector2f leftBottom = new Vector2f(0, height);
            Vector2f rightBottom = new Vector2f(width, height);

            Vector4f temp = new Vector4f();
            Matrix4f.transform(matrix, new Vector4f(leftTop.x, leftTop.y, 0, 1), temp);
            leftTop.x = temp.x;
            leftTop.y = temp.y;
            Matrix4f.transform(matrix, new Vector4f(rightTop.x, rightTop.y, 0, 1), temp);
            rightTop.x = temp.x;
            rightTop.y = temp.y;
            Matrix4f.transform(matrix, new Vector4f(leftBottom.x, leftBottom.y, 0, 1), temp);
            leftBottom.x = temp.x;
            leftBottom.y = temp.y;
            Matrix4f.transform(matrix, new Vector4f(rightBottom.x, rightBottom.y, 0, 1), temp);
            rightBottom.x = temp.x;
            rightBottom.y = temp.y;

            left = (int) Math.min(Math.min(leftTop.x, rightTop.x), Math.min(leftBottom.x, rightBottom.x));
            top = (int) Math.min(Math.min(leftTop.y, rightTop.y), Math.min(leftBottom.y, rightBottom.y));
            right = (int) Math.max(Math.max(leftTop.x, rightTop.x), Math.max(leftBottom.x, rightBottom.x));
            bottom = (int) Math.max(Math.max(leftTop.y, rightTop.y), Math.max(leftBottom.y, rightBottom.y));
		}
	}

	/**
	 * Checks whether the bounding box collides with the other's bounding box.
	 * @param other the other collidable object.
	 * @return whether or not the two objects collide.
	 */
	public boolean checkBoundingBoxCollision(CollisionMask other) {
		calculateBoundingBox();
		other.calculateBoundingBox();
		if (bottom < other.top)
			return false;
		if (top > other.bottom)
			return false;
		if (right < other.left)
			return false;
		if (left > other.right)
			return false;
		return true;
	}

	/**
	 * Checks whether or not this object's bounding box collides with a point.
	 * @param x2 the x position of the point to check.
	 * @param y2 the y position of the point to check.
	 * @return whether or not the object collides with a point.
	 */
	public boolean checkBoundingBoxCollision(float x2, float y2) {
		calculateBoundingBox();
		if ((int)x2 >= right || (int)x2 < left)
			return false;
		if ((int)y2 >= bottom || (int)y2 < top)
			return false;
		return true;
	}

	/**
	 * Whther or not the object collides with another object.
	 * @param other the other object.
	 * @return whether or not the two objects collide.
	 */
	public boolean collidesWith(CollisionMask other) {
		if (checkBoundingBoxCollision(other)) {
			if ((rotation == 0 || rotation == 180) && scale == 1f && !xmirror && !ymirror) {
				for (int yy = top; yy < bottom; yy++)
		        {
		            for (int xx = left; xx < right; xx++)
		            {
		                // Get the existance of pixel at area
		                boolean a = data.getPixel(xx - left, yy - top);
		                boolean b = other.data.getPixel(xx - other.left, yy - other.top);

		                // If both pixels are in the mask
		                if (a && b)
		                {
		                    // then an intersection has been found
		                    return true;
		                }
		            }
		        }
			}else {
				Vector2f stepX = new Vector2f();
				Vector2f stepY = new Vector2f();

				Matrix4f transformAtoB = new Matrix4f();
				Matrix4f.mul(matrix, (Matrix4f)other.matrix.invert(), transformAtoB);

				Vector4f temp = new Vector4f();
	            Matrix4f.transform(transformAtoB, new Vector4f(1, 0, 0, 0), temp);
	            stepX.x = temp.x;
	            stepX.y = temp.y;
	            Matrix4f.transform(transformAtoB, new Vector4f(0, 1, 0, 0), temp);
	            stepY.x = temp.x;
	            stepY.y = temp.y;

	            Vector2f yPosInB = new Vector2f();
	            Matrix4f.transform(transformAtoB, new Vector4f(0, 0, 0, 1), temp);
	            yPosInB.x = temp.x;
	            yPosInB.y = temp.y;

	            for (int yA = 0; yA < height; yA++) {
	            	Vector2f posInB = new Vector2f();
	            	posInB.x = yPosInB.x; //possibly sketch
	            	posInB.y = yPosInB.y;

	            	for (int xA = 0; xA < width; xA++) {
	            		int xB = (int)Math.round(posInB.x);
	                    int yB = (int)Math.round(posInB.y);

	                    Matrix4f.transform(matrix, new Vector4f(xA, yA, 0, 1), temp);
	                    System.out.println("A:(" + temp.x + ", " + temp.y + ")");
	                    Matrix4f.transform((Matrix4f)other.matrix.invert(), new Vector4f(temp.x, temp.y, 0, 1), temp);
	                    System.out.println("B:(" + temp.x + ", " + temp.y + ")");
	                    if (0 <= xB && xB < other.width && 0 <= yB && yB < other.height)
                        {
                            // Get the colors of the overlapping pixels
                            boolean a = data.getPixel(xA, yA);
                            boolean b = data.getPixel(xB, yB);
                            System.out.println("(" + xB + ", " + yB + ")");

                            // If both pixels are not completely transparent,
                            if (a && b)
                            {
                                // then an intersection has been found
                                return true;
                            }
                        }

                        // Move to the next pixel in the row
	                    Vector2f.add(posInB, stepX, posInB);
	            	}
	            	Vector2f.add(yPosInB, stepY, yPosInB);
	            }
			}
		}
		return false;
	}

	/**
	 * Checks whether the object collides with a point.
	 * @param x2 the x position of the point.
	 * @param y2 the y position of the point.
	 * @return whether or not the object collides with the point.
	 */
	public boolean collidesWith(float x2, float y2) {
		if (checkBoundingBoxCollision(x2, y2))
			if ((rotation == 0 || rotation == 180) && scale == 1f && !xmirror && !ymirror)
				return data.getPixel((int)x2 - left, (int)y2 - top);
			else {
				Matrix4f transformWorldToA = (Matrix4f)matrix.invert();
//				System.out.println(transformWorldToA);
				Vector4f temp = new Vector4f();
	            Matrix4f.transform(transformWorldToA, new Vector4f(x2, y2, 0, 1), temp);

//	            System.out.println("(" + (x2 - x + width / 2) + ", " + (y2 - y + height / 2) + ") -> (" + (int)temp.x + ", " + (int)temp.y + ")");
	            return data.getPixel((int)temp.x, (int)temp.y);
			}
		return false;
	}

	/**
	 * Prints out the collision mask.
	 */
	public String toString() {
		return "left: " + left + " right: " + right + " top: " + top + " bottom: " + bottom;
	}
}
